package ringlist

import (
	_ "fmt"
	"gitee.com/kingzyt/common/objPool"
)

type RingListInternalList interface {
	GetLen() int
	GetCap() int
	NewList(length int) RingListInternalList
	GetElem(idx int) interface{}
	SetElem(idx int, elem interface{})
	Clear() // release all hold resources
}
type defaultInternalList struct {
	list []interface{}
}

func (self *defaultInternalList) GetLen() int {
	return len(self.list)
}
func (self *defaultInternalList) GetCap() int {
	return cap(self.list)
}
func (self *defaultInternalList) NewList(length int) RingListInternalList {
	return &defaultInternalList{list: make([]interface{}, length)}
}
func (self *defaultInternalList) GetElem(idx int) interface{} {
	return self.list[idx]
}
func (self *defaultInternalList) SetElem(idx int, elem interface{}) {
	self.list[idx] = elem
}
func (self *defaultInternalList) Clear() {
	for i := 0; i < len(self.list); i++ {
		self.list[i] = nil
	}
}

type RingList struct {
	List    RingListInternalList
	HeadIdx int
	TailIdx int
}

func NewRingList(initList RingListInternalList, initCap int) *RingList {
	if initCap <= 0 {
		initCap = 2
	}
	list := initList
	if initList == nil {
		list = &defaultInternalList{list: make([]interface{}, initCap)}
	} else {
		if initList.GetLen() < initCap {
			list = initList.NewList(initCap)
		}
	}
	return &RingList{
		List:    list,
		HeadIdx: 0,
		TailIdx: 0,
	}
}

func (self *RingList) moveHead() {
	self.HeadIdx = (self.HeadIdx + 1) % self.List.GetLen()
}
func (self *RingList) moveTail(elem interface{}) {
	self.List.SetElem(self.TailIdx, elem)
	self.TailIdx = (self.TailIdx + 1) % self.List.GetLen()
}

func (self *RingList) PeekHead() interface{} {
	if self.HeadIdx == self.TailIdx {
		return nil
	} else {
		return self.List.GetElem(self.HeadIdx)
	}
}
func (self *RingList) PopHead() interface{} {
	if self.HeadIdx == self.TailIdx {
		return nil
	} else {
		idx := self.HeadIdx
		self.moveHead()
		popedElem := self.List.GetElem(idx)
		self.List.SetElem(idx, nil)
		return popedElem
	}
}

func (self *RingList) PushTail(elem interface{}) {
	if self.HeadIdx == self.TailIdx {
		self.moveTail(elem)
	} else if self.HeadIdx > self.TailIdx {
		if self.TailIdx+1 == self.HeadIdx {
			oldLen := self.List.GetLen()
			newLen := 0
			if oldLen > 1024 {
				newLen = oldLen + 1024
			} else {
				newLen = oldLen * 2
			}
			newList := self.List.NewList(newLen)
			newIdx := 0
			for i := self.HeadIdx; i < oldLen; i++ {
				newList.SetElem(newIdx, self.List.GetElem(i))
				newIdx++
			}
			for i := 0; i < self.TailIdx; i++ {
				newList.SetElem(newIdx, self.List.GetElem(i))
				newIdx++
			}
			self.List.Clear()
			//fmt.Printf("extend %v =>%v\n", self.List, newList)
			self.List = newList
			self.HeadIdx = 0
			self.TailIdx = newIdx
		}
		self.moveTail(elem)
	} else {
		if (self.TailIdx+1)%self.List.GetLen() == self.HeadIdx {
			oldLen := self.List.GetLen()
			newList := self.List.NewList(oldLen * 2)
			newIdx := 0
			for i := self.HeadIdx; i < self.TailIdx; i++ {
				newList.SetElem(newIdx, self.List.GetElem(i))
				newIdx++
			}
			self.List.Clear()
			//fmt.Printf("extend %v =>%v\n", self.List, newList)
			self.List = newList
			self.HeadIdx = 0
			self.TailIdx = newIdx
		}
		self.moveTail(elem)
	}
}
func (self *RingList) Len() int {
	if self.HeadIdx > self.TailIdx {
		return self.List.GetLen() - (self.HeadIdx - self.TailIdx)
	} else {
		return self.TailIdx - self.HeadIdx
	}
}

func (self *RingList) Cap() int {
	return self.List.GetCap()
}

func (self *RingList) Clear() {
	self.HeadIdx = 0
	self.TailIdx = 0

	listLen := self.List.GetLen()
	for i := 0; i < listLen; i++ {
		self.List.SetElem(i, nil)
	}
}

func (self *RingList) IteratorElems(proc func(v interface{}) bool) {
	if proc == nil {
		return
	}

	if self.HeadIdx == self.TailIdx {
		return
	} else if self.HeadIdx > self.TailIdx {
		listLen := self.List.GetLen()
		for i := self.HeadIdx; i < listLen; i++ {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
		for i := 0; i < self.TailIdx; i++ {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
	} else {
		for i := self.HeadIdx; i < self.TailIdx; i++ {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
	}
}

func (self *RingList) IteratorRevElems(proc func(v interface{}) bool) {
	if proc == nil {
		return
	}

	if self.HeadIdx == self.TailIdx {
		return
	} else if self.HeadIdx > self.TailIdx {
		for i := self.TailIdx - 1; i >= 0; i-- {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
		listLen := self.List.GetLen()
		for i := listLen - 1; i >= self.HeadIdx; i-- {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
	} else {
		for i := self.TailIdx - 1; i >= self.HeadIdx; i-- {
			if !proc(self.List.GetElem(i)) {
				return
			}
		}
	}
}

func (self *RingList) IsContains(elem interface{}) bool {
	found := false
	self.IteratorElems(func(v interface{}) bool {
		if v == elem {
			found = true
		}
		return !found
	})
	return found
}

var G_tempRingListPool = objPool.NewObjPool("tempRingList", 0, 0, func() interface{} {
	return NewRingList(nil, 0)
})

// if need call remove, elem should be comparable obj, int/string/pointer...
// but slice and map is uncomparable, will panic
func (self *RingList) Remove(elem interface{}) bool {
	if self.Len() == 0 {
		return false
	}

	tempList := G_tempRingListPool.Pull().(*RingList)
	defer G_tempRingListPool.Push(tempList)
	tempList.Clear()

	self.IteratorElems(func(v interface{}) bool {
		if v != elem {
			tempList.PushTail(v)
		}
		return true
	})

	self.Clear()

	var head interface{}
	head = tempList.PopHead()
	for head != nil {
		self.PushTail(head)
		head = tempList.PopHead()
	}

	tempList.Clear()

	return true
}

func (self *RingList) RemoveElems(judgeRemoveProc func(elem interface{}) (needRemove bool)) bool {
	if self.Len() == 0 {
		return false
	}

	tempList := G_tempRingListPool.Pull().(*RingList)
	defer G_tempRingListPool.Push(tempList)
	tempList.Clear()

	self.IteratorElems(func(v interface{}) bool {
		if !judgeRemoveProc(v) {
			tempList.PushTail(v)
		}
		return true
	})

	self.Clear()

	var head interface{}
	head = tempList.PopHead()
	for head != nil {
		self.PushTail(head)
		head = tempList.PopHead()
	}

	tempList.Clear()

	return true
}
